package function;

import org.junit.Test;

import java.util.Arrays;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @Author C.
 * @Date 2019-03-31 13:48
 */
public class FunctionTest {

    /**
     * 有个函数式接口Consumer，里面有个抽象方法accept能够接收一个参数但是没有返回值，
     * 这个时候我想实现accept方法，让它的功能为打印接收到的那个参数，那么我可以使用Lambda表达式这么做
     */
    @Test
    public void test_lambda() {
        Consumer<String> consumer = str -> System.out.println(str);
        consumer.accept("使用方法引用之前");
    }

    /**
     * 上面的accept方法跟println方法是不是很像，都是接收一个参数类型为String参数，并且无返回值
     * 方法引用的规定，实现抽象方法的参数列表，必须与方法引用方法的参数列表保持一致！至于返回值就不作要求
     */
    @Test
    public void test_function() {
        Consumer<String> consumer = System.out::println;
        consumer.accept("使用方法引用");
    }

    /**
     * 先来说一下方法引用使用到的操作符“::”，这个操作符把方法引用分成两边，
     * 左边是类名或者某个对象的引用，右边是方法名或者是“new”（构造器引用时用到）
     *
     * 方法引用可以引用的不止是方法
     *
     * 有以下3种引用
     *      引用方法
     *      引用构造器
     *      引用数组
     */


    /**
     * 1.引用方法
     * 引用方法有下面几种方式
     *      对象引用::实例方法名
     *      类名::静态方法名
     *      类名::实例方法名
     */

    /**
     * 对象引用::实例方法名
     * <p>
     * System.out就是一个PrintStream类型的对象引用，而println则是一个实例方法名，需要注意的是没有括号的。
     * 其中Consumer是Java内置函数式接口，下面的例子用到的都是Java内置函数式接口。
     * Consumer中的唯一抽象方法accept方法参数列表与println方法的参数列表相同，都是接收一个String类型参数。
     */
    @Test
    public void test_function_01() {
        //Consumer<String> consumer = x -> System.out.println(x);
        Consumer<String> consumer = System.out::println;
        consumer.accept("对象引用::实例方法名");
    }

    /**
     * 类名::静态方法名
     * <p>
     * Math是一个类而abs为该类的静态方法。
     * Function中的唯一抽象方法apply方法参数列表与abs方法的参数列表相同，都是接收一个Long类型参数。
     */
    @Test
    public void test_function_02() {
        //Function<Long, Long> f = x -> Math.abs(x);
        Function<Long, Long> f = Math::abs;
        Long result = f.apply(-3L);
        System.out.println(result);
    }


    /**
     * 类名::实例方法名
     * <p>
     * 若Lambda表达式的参数列表的第一个参数，是实例方法的调用者，第二个参数(或无参)是实例方法的参数时，就可以使用这种方法
     * <p>
     * String是一个类而equals为该类的定义的实例方法。
     * BiPredicate中的唯一抽象方法test方法参数列表与equals方法的参数列表相同，都是接收两个String类型参数。
     */
    @Test
    public void test_function_03() {
        //BiPredicate<String, String> b = (x,y) -> x.equals(y);
        BiPredicate<String, String> b = String::equals;
        System.out.println(b.test("abc", "abcd"));
    }


    /**
     * 2.引用构造器
     * <p>
     * 在引用构造器的时候，构造器参数列表要与接口中抽象方法的参数列表一致,格式为 类名::new
     * <p>
     * <p>
     * Function接口的apply方法接收一个参数，并且有返回值。
     * 在这里接收的参数是Integer类型，与StringBuffer类的一个构造方法StringBuffer(int capacity)对应，
     * 而返回值就是StringBuffer类型。这段代码的功能就是创建一个Function实例，
     * 并把它apply方法实现为创建一个指定初始大小的StringBuffer对象。
     */
    @Test
    public void test_construction() {
        //Function<Integer, StringBuffer> fun = n -> new StringBuffer(n);
        Function<Integer, StringBuffer> fun = StringBuffer::new;
        StringBuffer buffer = fun.apply(10);
        System.out.println(buffer);
    }

    /**
     * 3.引用数组
     * <p>
     * 引用数组和引用构造器很像，格式为 类型[]::new，其中类型可以为基本类型也可以是类
     */
    @Test
    public void test_array() {
        // Function<Integer, int[]> fun = n -> new int[n];
        Function<Integer, int[]> fun = int[]::new;
        int[] arr = fun.apply(10);
        System.out.println(Arrays.toString(arr));
        Function<Integer, Integer[]> fun2 = Integer[]::new;
        Integer[] arr2 = fun2.apply(10);
        System.out.println(Arrays.toString(arr2));
    }
}
